/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.game;

import com.serponix.game.food.Cake;
import com.serponix.game.food.Cherry;
import com.serponix.game.objektyNaPlose.Clanek;
import com.serponix.game.objektyNaPlose.VecNaPolicku;
import com.serponix.game.objektyNaPlose.ViceObjektu;
import com.serponix.game.objektyNaPlose.Zed;
import com.serponix.game.projectiles.Laser;
import com.serponix.game.projectiles.Rocket;
import com.serponix.game.score.Score;
import com.serponix.net.Informace;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Had obsahuje informace o poloze všech jeho článků, směru a vlastnostech.
 *
 * @author Daniel Vala
 */
public final class Snake {

	private final int MINIMUM_CELLS = 5;
	private final int NUMBER_OF_EXTRA_CELLS = 12;
	public Score score;
	private Player player;
	private List<Clanek> clanky;
	private int startX, startY, tloustka;
	private Color color;
	/**
	 * Actual direction of the snake (where snake will go his next move).
	 */
	private Direction direction;
	/**
	 * if snake do not move, had some direction before stoping (other then NOWHERE).
	 */
	private Direction directionBeforeStop;
	private GameModel model;
	private List<Boolean> zasobniky;
	private boolean rocketLoaded;
	private int pocetRaket;
	private int startVitality;
	private int vitality;
	private boolean stopOneRound;
	private boolean strelbySchopny;
	private boolean jedoveStrely, invulnerability, totalInvulnerability, tank, turtle, obraceneKlavesy, svobodny, duch, tron, neviditelnost, pstros, slowdown;
	// counters
	private int tankCounter, jedoveStrelyCounter, obraceneKlavesyCounter, odolnostCounter, duchCounter, neviditelnostCounter, zpomalenyCounter, totalIlvulnerabilityCounter;
	private String tronInfo = "";
	private boolean blikajici, blikajiciTemp;
	private String pathTextura;
	private int pocatecniDelka;
	private int velikostProVitezstvi, velikostProVitezstviTemp;
	// mrtvý had nemá žádné vlastnosti a je v počáteční velikosti
	private boolean zije;

	/**
	 * Kopírovací kontruktor vytvoří hada podle hada předaného v parametru konstruktoru.
	 * Vytvoří ho se stejnými parametry, jako měl předaný had při vytvoření a zkopíruje jeho aktuální články.
	 *
	 * @param had Had, podle kterého se bude vytvářet had nový.
	 */
	public Snake(Snake had) {
		this(had.model, had.player, had.startX, had.startY, had.direction, had.startVitality, had.tloustka, had.pocatecniDelka, had.velikostProVitezstvi, had.getBarva(), had.pathTextura, had.strelbySchopny, had.blikajici);
		clanky = had.clanky;
	}

	/**
	 * Vytvoří nového hada.
	 *
	 * @param model                Model hry, ve kterém bude had existovat.
	 * @param jmeno                Jméno hada (hráče hry).
	 * @param startX               Počáteční pozice hada x.
	 * @param startY               Počáteční pozice hada y.
	 * @param direction            Počáteční směr hada.
	 * @param startVitality        Počáteční růst hada.
	 * @param tloustka             Definuje, jak bude had široký.
	 * @param pocatecniDelka       Počáteční počet článků hada.
	 * @param velikostProVitezstvi Pokud had dosáhne dané velikosti, zvítězí. Pro neomezenou velikost nastavte 0.
	 * @param barva                Barva hada.
	 * @param pathTextura          Textura hada.
	 * @param strelbySchopny       Definuje, zda bude had schopen střelby.
	 * @param blikajici            Definuje, zda bude hadovi při velikosti blízké k vítězství blikat ocas.
	 */
	public Snake(GameModel model, Player hrac, int startX, int startY, Direction direction, int startVitality, int tloustka, int pocatecniDelka, int velikostProVitezstvi, Color barva, String pathTextura, boolean strelbySchopny, boolean blikajici) {
		zije = true;
		this.player = hrac;
		this.model = model;
		this.startX = startX;
		this.startY = startY;
		this.direction = direction;
		this.startVitality = startVitality;
		this.tloustka = tloustka;
		this.pocatecniDelka = pocatecniDelka;
		this.velikostProVitezstvi = velikostProVitezstvi;
		this.color = barva;
		this.pathTextura = pathTextura;
		this.strelbySchopny = strelbySchopny;
		this.blikajici = blikajici;
		score = new Score(this, model);

		// vytvoření LinkedListu a vložení nových článků podle startovní pozice a počáteční velikosti hada
		clanky = new LinkedList<Clanek>();
		for (int i = 0; i < pocatecniDelka; i++) {
			clanky.add(new Clanek(this, startX, startY));
		}
		resetAbilities();
		rocketLoaded = true;
	}

	/**
	 * Vrátí seznam souřadnic předaných článků.
	 *
	 * @param clanky Články, z kterých chceme zjistit souřadnice.
	 * @return Seznam souřadnic článků v poli Dimension.
	 */
	private static List<Dimension> getDimensionFromClanky(List<Clanek> clanky) {
		List<Dimension> souradniceClanku = new ArrayList<Dimension>();
		for (Clanek clanek : clanky) {
			souradniceClanku.add(new Dimension(clanek.getX(), clanek.getY()));
		}
		return souradniceClanku;
	}

	/**
	 * Kopírování článků podle zadaných souřadnic do hada.
	 *
	 * @param souradniceClanku Souřadnice, které budou použity pro tvorbu nových článků hada.
	 * @param had              Had, který dostane nové články podle zadaných souřadnic.
	 */
	private static void swapAllClanky(List<Dimension> souradniceClanku, Snake had) {
		List<Clanek> noveClanky = new ArrayList<Clanek>();
		for (Dimension dimension : souradniceClanku) {
			noveClanky.add(new Clanek(had, (int) dimension.getWidth(), (int) dimension.getHeight()));
		}
		had.clanky = noveClanky;
	}

	/**
	 * Kopírování článů z hada1 do hada2.
	 * Had2 si vytvoří své nové články podle počtu a souřadnic hada1.
	 *
	 * @param had1 Had, jehož články budou použity k vytvoření článků hadaě.
	 * @param had2 Had, který bude prohozen s hadem1.
	 */
	private static void swapAllClanky(Snake had1, Snake had2) {
		List<Clanek> noveClanky = new ArrayList<Clanek>();
		for (Clanek clanek : had1.clanky) {
			noveClanky.add(new Clanek(had2, clanek.getX(), clanek.getY()));
		}
		had2.clanky = noveClanky;
	}

	@Override
	public String toString() {
		return getJmeno();
	}

	/**
	 * Vrátí jméno hráče, který ovládá tohoto hada.
	 *
	 * @return jméno hráče, který ovládá tohoto hada.
	 */
	public String getJmeno() {
		return player.getJmeno();
	}

	/**
	 * Zjistí, jakému hráči had patří.
	 *
	 * @return Hráč, kterému had patří.
	 */
	public Player getHrac() {
		return player;
	}

	/**
	 * Zjistí x souřadnici hlavy hada.
	 *
	 * @return X souřadnice hlavy.
	 */
	public int getX() {
		return clanky.get(0).getX();
	}

	/**
	 * Zjistí y souřadnici hlavy hada.
	 *
	 * @return Y souřadnice hlavy.
	 */
	public int getY() {
		return clanky.get(0).getY();
	}

	public int getXProVzhled() {
		return getX() * tloustka;
	}

	public int getYProVzhled() {
		return getY() * tloustka;
	}

	public void odstranClanek(int cisloClanku) {
		clanky.get(cisloClanku).odmapuj(model);
		clanky.remove(cisloClanku);
	}

	/**
	 * Zjistí aktuální délku hada.
	 *
	 * @return Aktuální délka (počet článků hada).
	 */
	public int getDelka() {
		return clanky.size();
	}

	/**
	 * Nastaví počet článků hada podle zadané hodnoty.
	 *
	 * @param delka Nová velikost hada.
	 */
	private void setDelkaHada(int delka) {
		if (getDelka() > delka) {
			usekniOcas(delka - 1);
		} else if (getDelka() < delka) {
			int narust = delka - getDelka();
			prodluzHada(narust);
		}
	}

	/**
	 * Had naroste o počet článků podle jeho proměnné nárust.
	 * Had musí mít alespoň jeden článek.
	 */
	public void prodluzHada() {
		prodluzHada(vitality);
	}

	/**
	 * Had naroste o zadaný počet článků.
	 * Nelze použít záporná čísla. Pro zkrácení použijte metodu zkratHada.
	 * Had musí mít alespoň jeden článek.
	 * Nový článek se umístí na stejné místo, jako je poslední článek.
	 * Na tomto místě tedy budou v tuto chvíli 2 nebo více článků.
	 *
	 * @param narust Počet článků, o které má had narůst.
	 */
	public void prodluzHada(int narust) {
		for (int i = 0; i < narust; i++) {
			Clanek posledniClanek = clanky.get(getDelka() - 1);
			clanky.add(new Clanek(this, posledniClanek.getX(), posledniClanek.getY()));
		}
		if (velikostProVitezstvi != 0 && getDelka() >= velikostProVitezstvi) {
			score.vyhra();
		}
	}

	/**
	 * Zkrátí hada o daný pocet kostiček. Vždy však zůstane ve své minimální velikosti.
	 * Nelze použít záporná čísla. Pro prodloužení hada použijte metodu narust.
	 *
	 * @param pocetKosticek Počet kostiček, o který se had zkrátí.
	 * @return true, pokud had důsledkem zkrácení zemřel. Jinak false.
	 */
	public boolean zkratHada(int pocetClanku) {
		// Pokud se má had zkrátit o tolik článků, že už by byl menší než jeho minimální velikost, zemře.
		if (getDelka() - MINIMUM_CELLS < pocetClanku) {
			smrt();
			return true;
		} else {
			for (int i = 0; i < pocetClanku; i++) {
				odstranClanek(getDelka() - 1);
			}
			return false;
		}
	}

	/**
	 * Return if snake is alive.
	 *
	 * @return True, if snake is alive. False, if snake is dead.
	 */
	public boolean isAlive() {
		return zije;
	}

	/**
	 * Return if snake is dead.
	 *
	 * @return True, if snake is dead. False, if snake is alive.
	 */
	public boolean isDead() {
		return !zije;
	}

	/**
	 * Zjistí barvu hada.
	 * Ne na všech článcích ale musí mít had stejnou barvu.
	 * Pro zjištění barvy hada na daném článku použijte getBarva(int clanekHada)
	 *
	 * @return Barva hada.
	 * @see getBarva(int clanekHada)
	 */
	public Color getBarva() {
		return color;
	}

	/**
	 * Zjistí barvu hada v jeho daném článku.
	 * Může být jiná, než při volání metody getBarva(), protože některé články mohou mít jinou barvu, než obecně had má.
	 *
	 * @param clanekHada Článek hada od 0 do délky hada - 1.
	 * @return Barva hada. Null pokud daný článek hada neexistuje.
	 * @see getBarva()
	 */
	public Color getBarva(int clanekHada) {
		if (clanekHada > getDelka() - 1) {
			return null;
		}
		if (blikajici && clanekHada > velikostProVitezstvi - NUMBER_OF_EXTRA_CELLS) {
			return clanekHada % 2 == 0 ? Color.RED : Color.BLUE;
		}
		return getBarva();
	}

	/**
	 * Set all abilities to a default state.
	 */
	public void resetAbilities() {
		setPocetZasobniku(1);
		pocetRaket = 0;
		vitality = startVitality;
		setVlastnostDuch(false);
		setVlastnostJedoveStrely(false);
		setVlastnostTurtle(false);
		setVlastnostSvoboda(false);
		setVlastnostObraceneKlavesy(false);
		setVlastnostPstros(false);
		setVlastnostIlvulnerability(false);
		setVlastnostTotalInvulnerability(false);
		setVlastnostTank(false);
		setVlastnostNeviditelnost(false);
		setVlastnostZpomaleni(false);
	}

	/**
	 * Pokud je had živý, umře. Ztratí schopnost pohybovat se a střílet.
	 * Pokud po smrti hada zůstane ve hře jen jediný živý had,
	 * stane se poslední živý had vítězem.
	 *
	 * @return <tt> true </tt> pokud po smrti hada skončilo kolo.
	 */
	public boolean smrt() {
		if (zije) {
			zije = false;
			model.getPanelZprav().addMessage(String.format("%s byl zabit!", getJmeno()));
			return model.isWinner();
		}
		return false;
	}

	/**
	 * Resetuje hada (nastaví do původních hodnot) a oživne ho.
	 * Vymaže skóre.
	 */
	public void oziv() {
		setDelkaHada(pocatecniDelka);
		resetAbilities();
		score.reset();
		rocketLoaded = true; // TODO melo by byt spise v resetVlastnosti
		zije = true;
	}

	/**
	 * Přesune hada na zadané souřadnice a nastaví mu zadaný směr.
	 *
	 * @param x    Nová souřadnice hlavy hada.
	 * @param y    Nová souřadnice hlavy hada.
	 * @param smer Nový směr hada.
	 */
	public void setHad(int x, int y, Direction smer) {
		odmapujVsechnyClanky();
		for (int i = 0; i < clanky.size(); i++) {
			clanky.get(i).move(x, y);
		}
		this.direction = smer;
	}

	/**
	 * Vrátí počet článků, který had musí mít, aby zvítězil.
	 *
	 * @return Počet článků hada k nutných k vítězství.
	 */
	public int getVelikostProVitezstvi() {
		return velikostProVitezstvi;
	}

	/**
	 * Vrátí aktuální směr hada.
	 *
	 * @return Aktuální směr.
	 */
	public Direction getSmer() {
		return direction;
	}

	/**
	 * Nastaví hadovi zadaný směr, ať je jakýkoliv.
	 * Nebere v potaz žádné stavy hada.
	 *
	 * @param smer Nový směr hada.
	 */
	public void setSmer(Direction smer) {
		this.direction = smer;
	}

	/**
	 * Vrátí směr hada, přicemž nikdy nevrátí směr NIKAM.
	 * Pokud tedy had aktuálně stojí, vrátí se směr před jeho zastavením.
	 *
	 * @return Aktuální směr nebo směr před zastavením hada.
	 */
	public Direction getExistingDirection() {
		if (isMoving()) {
			return direction;
		} else {
			return directionBeforeStop;
		}
	}

	/**
	 * Return actual direction of snake.
	 *
	 * @return Actual direction of snake.
	 */
	public Direction getDirection() {
		return direction;
	}

	/**
	 * If snake is stopped, return direction, which snake had before stopping.
	 * If snake is moving, return null;
	 *
	 * @return Direction before stopping or null, if snake is moving.
	 */
	public Direction getDirectionBeforeStop() {
		if (isStopped()) {
			return directionBeforeStop;
		} else {
			return null;
		}
	}

	/**
	 * Změní směr hada, přičemž nedovoluje změnit směr na opačný, než je právě nastaven.
	 * Pokud má ale had schopnost zastavit, tak při nastavení opačného směru had zastaví.
	 * Jestliže je had mrtev, není schopen měnit směr.
	 * Pokud má had obrácené klávesy, nastaví směr opačně, než je zadáno.
	 * Pro potřeby změnit směr hada natvrdo lze použít veřejnou proměnou smer.
	 *
	 * @param smer Nový směr hada.
	 */
	public void changeSmer(Direction direction) {
		// pokud had nežije, nemá možnost měnit svůj směr
		if (isDead()) {
			return;
		}

		if (obraceneKlavesy) {
			direction = Direction.obratSmer(direction);
		}

		// pokud je směr opačný než aktuální, nebo při směru NIKAM se had pokusí zakuklit
		if (Direction.obratSmer(direction) == this.direction || direction == Direction.NOWHERE) {
			cocon();
			return;
		}

		// pokud had stojí, nelze změnit směr na směr opačný, který měl had před zastavením,
		// protože by naboural sám do sebe
		if (isStopped() && Direction.obratSmer(direction) == getDirectionBeforeStop()) {
			return;
		}
		this.direction = direction; // nastavení směru za normálních podmínek
	}

	private boolean isStopped() {
		return direction == Direction.NOWHERE;
	}

	private boolean isMoving() {
		return direction != Direction.NOWHERE;
	}

	/**
	 * If snake has ability turtle, he stops and gets a ability total invulnerability.
	 *
	 * @return True, if snake stopped. False, if calling this method had no effect.
	 * It means is snake is already stopped, return false. Snake cannot cocon when is already stopped.
	 */
	private boolean cocon() {
		if (turtle && stop()) {
			setVlastnostTotalInvulnerability(true);
			totalIlvulnerabilityCounter = 30;
			return true;
		}
		return false;
	}

	/**
	 * Snake stops on his actual position.
	 *
	 * @return True, if snake stopped from moving. False, if snake was already stopped.
	 */
	public boolean stop() {
		if (isMoving()) {
			directionBeforeStop = direction;
			direction = Direction.NOWHERE;
			return true;
		}
		return false;
	}

	/**
	 * Vrátí číslo článku, který leží na zadaných souřadnicích. <br />
	 * 0 = hlava <br />
	 * 1 = jeden článek za hlavou atd. <br />
	 *
	 * @param x Souřadnice x článku.
	 * @param y Souřadnice y článku.
	 * @return Číslo článku hada, který leží na zadaných souřadnicích nebo
	 * -1 pokud na zadaných souřadnicích žádný článek není.
	 */
	public int getClanek(int x, int y) {
		for (int i = 0; i < clanky.size(); i++) {
			if (clanky.get(i).getX() == x && clanky.get(i).getY() == y) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Zasáhne článek hada, který se nachází v daných souřadnicích.
	 *
	 * @param laser Laser, který zasáhne daný článek.
	 * @param x     Souřadnice zasaženého článku x.
	 * @param y     Souřadnice zasaženého článku y.
	 * @return true, pokud byl zásah úspěšný. Jinak false.
	 */
	public boolean zasah(Laser laser, int x, int y) {
		int cisloClanku = getClanek(x, y);
		if (cisloClanku != -1) {
			return zasah(laser, cisloClanku);
		} else {
			return false;
		}
	}

	/**
	 * Had v daném článku obdrží zásah laserem a článek zmizí.
	 * Metoda bere v úvahu vlastnost odolnost, při kterém článek hada
	 * zůstane zachován a střela se odrazí
	 * a vlastnost duch, při kterém střela projde skrz.
	 * Pokud je střela smrtící, resetuje hada.
	 * Pokud je zasažena hlava hada, had se resetuje.
	 *
	 * @param laser       Laser, který zasáhl hada.
	 * @param cisloClanku Článek, který laser zasáhl.
	 * @return true, pokud hada zásah poškodil, jinak false.
	 */
	public boolean zasah(Laser laser, int cisloClanku) {
		if (duch) {
			return false;
		}
		//        if (tron) {
		//            if (cisloClanku > 0 && cisloClanku < getDelka()) {
		//                clanky.remove(cisloClanku);
		//                laser.znicSe();
		//            }
		//            tronInfo = "PAL " + cisloClanku;
		//            return true;
		//        }
		// in case of hittng head and not having totalInvulnerability, laser resets snake
		if (cisloClanku == 0 && !totalInvulnerability) {
			if (getDelka() > 1) {
				odstranClanek(0);
			}
			model.resetHada(this);
			if (this.getJmeno().equals(laser.getJmenoVlastnika())) {
				model.getPanelZprav().addMessage(String.format("%s dostal headshot sám od sebe!", this.getJmeno()));
			} else {
				model.getPanelZprav().addMessage(String.format("%s dostal headshot od hráče %s", this.getJmeno(), laser.getJmenoVlastnika()));
			}
			return true;
		}
		// in case of invulnerability and not hitting head is laser bounced
		// in case of totalInvulnerability (and not invulnerability) and not hitting extra cells, laser is also bounced
		if (invulnerability || (totalInvulnerability && cisloClanku <= (velikostProVitezstvi - NUMBER_OF_EXTRA_CELLS))) {
			laser.odrazSe();
			return false;
		}
		// jestliže daný článek hada existuje, odstraní se a střela zanikne
		if (cisloClanku > 0 && cisloClanku < getDelka()) {
			odstranClanek(cisloClanku);
		}
		// jestliže byla střela smrtící, had se přesune na startovní pozici
		if (laser.isSmrtici()) {
			model.resetHada(this);
		}
		// zkontrolování velikosti hada a případná smrt
		checkSmrt();
		return true;
	}

	/**
	 * Usekne ocas v článku podle zadaného indexu.
	 *
	 * @param index Index článku, kde se má had useknout.
	 */
	public void usekniOcas(int index) {
		if (index >= 0 && index < getDelka()) {
			while (index < clanky.size() - 1) {
				odstranClanek(index);
			}
		}
	}

	public void usekniOcas(int x, int y) {
		usekniOcas(getClanek(x, y));
	}

	/**
	 * Usekne ocas v článku podle zadaného indexu tak, že z ocasu zbyde polovina. Každý druhý článek useklého ocasu zůstane.
	 * Pokud je had duchem, nic se nestane.
	 *
	 * @param index Index článku, kde se má had useknout.
	 */
	public void usekniEfektneOcas(int index) {
		if (duch) {
			return;
		}
		if (index >= 0 && index < getDelka()) {
			if (tron) {
				usekniOcas(index);
			}
			for (int i = index; i < clanky.size() - 1; i++) {
				odstranClanek(i);
			}
			checkSmrt();
		}
	}

	public void usekniEfektneOcas(int x, int y) {
		usekniEfektneOcas(getClanek(x, y));
	}

	/**
	 * Zkontroluje, zda má had stale potřebnou délku k přežití.
	 * V opačném případě ho zabije.
	 *
	 * @return <tt> true </tt> pokud had zemřel.
	 */
	private boolean checkSmrt() {
		if (getDelka() < MINIMUM_CELLS) {
			smrt();
			return true;
		}
		return false;
	}

	/**
	 * Pokud je had naživu a v současné chvíli schopný střelby, vystřelí laser.
	 *
	 * @return <tt>true</tt> pokud vystřelil.
	 */
	public boolean strelLaser() {
		if (isAlive() && strelbySchopny) {
			for (int i = 0; i < getPocetZasobniku(); i++) { // pro tento počet zásobníků
				if (zasobniky.get(i) == true) { // jestliže je v daném zásobníku připravená střela, had vystřelí
					model.addStrela(new Laser(model, this, getX(), getY(), getExistingDirection(), 3, i, isVlastnostJedoveStrely()));
					zasobniky.set(i, false); // střela už není v zásobníku, nelze ji tedy z něj vystřelit
					return true; // po vystřelení už není potřeba procházet další zásobníky
				}
			}
		}
		return false;
	}

	////////////////////////////////////////////////////////////////////////
	/////////////////////////  VLASTNOSTI HADA /////////////////////////////
	////////////////////////////////////////////////////////////////////////
	//                                                                    //
	///////////////////////////////   IS   /////////////////////////////////

	/**
	 * Pokud had žije, vlastní raketu a je schopný ji vystřelit, vystřelí ji.
	 *
	 * @return <tt>true</tt> pokud vystřelil.
	 */
	public boolean strelRaketu() {
		if (isAlive() && strelbySchopny && pocetRaket > 0) {
			if (rocketLoaded) {
				model.addStrela(new Rocket(model, this, getX(), getY(), getExistingDirection(), 2));
				rocketLoaded = false;
				pocetRaket--;
				return true;
			}
		}
		return false;
	}

	/**
	 * Nabije daný zásobník laserem. Bude tedy možné znovu z něj vystřelit.
	 *
	 * @param poziceVZasobniku Pozice v zásobníku od 0 do počtu počtu zásobníků - 1.
	 */
	public void nabijZasobnik(int poziceVZasobniku) {
		// zkontroluje, zda pozice zásobníku existuje
		if (poziceVZasobniku >= 0 && poziceVZasobniku < getPocetZasobniku()) {
			zasobniky.set(poziceVZasobniku, true);
		} else {
			throw new RuntimeException("Nabíjení neexistujícího zásobníku. Číslo zásobníku: " + poziceVZasobniku);
			// TODO vyhodit vlastni vyjimku pokus o nabijeni neexistujiciho zasobniku a logovat
		}
	}

	/**
	 * Nabije novou raketu. Pokud had tedy má ještě vice než 0 raket, bude moci jednu vystřelit.
	 */
	public void nabijRaketu() {
		rocketLoaded = true;
	}

	/**
	 * Zjistí, jestli je had duch.
	 *
	 * @return <tt>true</tt> pokud je had duch.
	 */
	public boolean isVlastnostDuch() {
		return duch;
	}

	/**
	 * Zjistí, zda má had střely napuštěné jedem.
	 *
	 * @return <tt>true</tt> pokud had má střely napuštené jedem.
	 */
	public boolean isVlastnostJedoveStrely() {
		return jedoveStrely;
	}

	/**
	 * Zjistí, zda je had schopný zastavit.
	 *
	 * @return <tt>true</tt>, pokud je had schopný zastavit.
	 */
	public boolean isVlastnostTurtle() {
		return turtle;
	}

	/**
	 * Zjistí, zda je had svobodný.
	 *
	 * @return <tt>true</tt> pokud je had svobodný.
	 */
	public boolean isVlastnostSvoboda() {
		return svobodny;
	}

	/**
	 * Zjistí, zda had má obrácené klávesy.
	 *
	 * @return <tt>true</tt> pokud had má obrácené klávesy.
	 */
	public boolean isVlastnostObraceneKlavesy() {
		return obraceneKlavesy;
	}

	/**
	 * Zjistí, zda je had pštrosem.
	 *
	 * @return <tt>true</tt> pokud had má vlastnost pštros.
	 */
	public boolean isVlastnostPstros() {
		return pstros;
	}

	/**
	 * Zjistí, zda je had odolný vůči střelám.
	 *
	 * @return <tt>true</tt> pokud had má vlastnost odolnost.
	 */
	public boolean isVlastnostOdolny() {
		return invulnerability;
	}

	///////////////////////////// GET ///////////////////////////////////////

	/**
	 * Zjistí, zda má had vlastnost tank.
	 *
	 * @return <tt>true</tt> pokud had má vlastnost tank.
	 */
	public boolean isVlastnostTank() {
		return tank;
	}

	/**
	 * Zjistí, zda je had neviditelný.
	 *
	 * @return <tt>true</tt> pokud je had neviditelný.
	 */
	public boolean isVlastnostNeviditelnost() {
		return neviditelnost;
	}

	/**
	 * Zjisti, zda je had zpomalený. Tzn. pohybuje se dvakrát pomaleji, než normálně.
	 *
	 * @return <tt>true</tt> pokud je had zpomalený.
	 */
	public boolean isVlastnostZpomaleny() {
		return slowdown;
	}

	///////////////////////////// ADD ///////////////////////////////////////

	/**
	 * Zjistí, kolik had může mít v jednu chvíli vystřelených laserů.
	 *
	 * @return Počet zásobníků.
	 */
	public int getPocetZasobniku() {
		return zasobniky.size();
	}

	/**
	 * Vytvoří zadaný počet nových zásobníků laseru. Staré smaže.
	 * Všechny zásobníky budou mít nabitý laser.
	 *
	 * @param pocet Počet zásobníků, který bude had vlastnit.
	 */
	private void setPocetZasobniku(int pocet) {
		zasobniky = new LinkedList<Boolean>(); // vytvoření zásobníku pro střely
		addZasobniky(pocet);
	}

	/**
	 * Zjistí, kolik má had aktuálně raket.
	 *
	 * @return Počet raket, který had vlastní.
	 */
	public int getPocetRaket() {
		return pocetRaket;
	}

	///////////////////////////// SET ///////////////////////////////////////

	/**
	 * Zjistí, jakou má had vitalitu a o kolik tedy roste při každém sežrání jídla.
	 *
	 * @return Počet článků, které hadovi narostou při sežrání jídla.
	 */
	public int getVitalita() {
		return vitality;
	}

	/**
	 * Přidá hadovi daný počet zásobníků na laser.
	 *
	 * @param pocet Počet zásobníků, který se přidá.
	 */
	public void addZasobniky(int pocet) {
		for (int i = 0; i < pocet; i++) {
			zasobniky.add(true);
		}
	}

	/**
	 * Přidá hadovi daný počet raket.
	 *
	 * @param pocet Počet raket, který had získá.
	 */
	public void addRakety(int pocet) {
		pocetRaket += pocet;
	}

	/**
	 * Zrychlí růst hada při každém sebrání jídla o daný počet článků.
	 *
	 * @param vitalita Počet článků, o který bude had růst.
	 */
	public void addVitalita(int vitalita) {
		this.vitality += vitalita;
	}

	/**
	 * Had se stane duchem na 600 kroků.
	 * Projde skrz všechny objekty jako jsou hadi a střely.
	 * Při požadavku na nastavení ducha na true je také zrušena vlastnost tank (pokud ji had měl).
	 *
	 * @param duch True, pokud had má být duchem, jinak false.
	 * @return True, pokud se vlastnost změnila. Jinak false.
	 */
	public boolean setVlastnostDuch(boolean duch) {
		if (duch) {
			tank = false;
		}
		if (duch) {
			duchCounter += 600;
		} else {
			duchCounter = 0;
		}
		boolean returnValue = this.duch != duch;
		this.duch = duch;
		return returnValue;
	}

	/**
	 * Had získá vlastnost smrtící střely na 1000 kroků.
	 * Kazdý vystřelený laser zasaženého hada včetně zničení jednoho článku resetuje.
	 *
	 * @param jedoveStrely True, pokud had má mít jedové střely, jinak false.
	 * @return True, pokud se vlastnost změnila. Jinak false.
	 */
	public boolean setVlastnostJedoveStrely(boolean jedoveStrely) {
		if (jedoveStrely) {
			jedoveStrelyCounter += 1000;
		} else {
			jedoveStrelyCounter = 0;
		}
		boolean returnValue = this.jedoveStrely != jedoveStrely;
		this.jedoveStrely = jedoveStrely;
		return returnValue;
	}

	/**
	 * Had získá schopnost zastavit.
	 * Směrovou klávesou opačného směru, než v jakém směru se had pohybuje, had zastaví.
	 *
	 * @param schopnostZastaveni True, pokud had má mít schopnost zastavení, jinak false.
	 * @return True, pokud se vlastnost změnila. Jinak false.
	 */
	public boolean setVlastnostTurtle(boolean schopnostZastaveni) {
		boolean returnValue = this.turtle != schopnostZastaveni;
		this.turtle = schopnostZastaveni;
		return returnValue;
	}

	/**
	 * Pokud je had svobodný a "narazí" do okraje herní plochy,
	 * objeví se zrcadlově na druhé straně.
	 * Při požadavku na nastavení svobody na true je také zrušena vlastnost pštros (pokud ji had měl).
	 *
	 * @param svoboda True, pokud had má mít svobodu. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostSvoboda(boolean svoboda) {
		if (svoboda) {
			pstros = false;
		}
		boolean returnValue = this.svobodny != svoboda;
		this.svobodny = svoboda;
		return returnValue;
	}

	/**
	 * Hadovi se obrátí směrové klávesy. Doprava bude doleva, nahoru bude dolu a naopak.
	 * Tato vlastnost je dočasná a to na 800 kroků.
	 *
	 * @param obraceneKlavesy True, pokud had má mít obrácené klávesy. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostObraceneKlavesy(boolean obraceneKlavesy) {
		if (obraceneKlavesy) {
			obraceneKlavesyCounter += 700;
		} else {
			obraceneKlavesyCounter = 0;
		}
		boolean returnValue = this.obraceneKlavesy != obraceneKlavesy;
		this.obraceneKlavesy = obraceneKlavesy;
		return returnValue;
	}

	/**
	 * Had s vlastností pštros nemůže narazit do krajní zdi.
	 * Může se pohybovat i mimo herní plochu.
	 *
	 * @param pstros True, pokud má mít had vlastnost pštros. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostPstros(boolean pstros) {
		boolean returnValue = this.pstros != pstros;
		this.pstros = pstros;
		return returnValue;
	}

	/**
	 * Had získá vlastnost odolnost na 1000 kroků.
	 * Všechny lasery se od hada odrazí.
	 * Při požadavku na nastavení odolnosti na true je také zrušena vlastnost duch (pokud ji had měl).
	 *
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostIlvulnerability(boolean invulnerability) {
		if (invulnerability) {
			duch = false;
			odolnostCounter += 1000;
		} else {
			odolnostCounter = 0;
		}
		boolean abilityChanged = this.invulnerability != invulnerability;
		this.invulnerability = invulnerability;
		return abilityChanged;
	}

	/**
	 * Snake gets or lost ability of total invulnerability.
	 * That means standard invulnerability (lasers are bounced) but moreover including head.
	 *
	 * @param totalInvulnerability Sets ability to true or false according to this parameter.
	 * @return True if ability was changed.
	 */
	public boolean setVlastnostTotalInvulnerability(boolean totalInvulnerability) {
		boolean abilityChanged = this.totalInvulnerability != totalInvulnerability;
		this.totalInvulnerability = totalInvulnerability;
		return abilityChanged;
	}

	////////////////////////////////////////////////////////////////////////
	//////////////////////  KONEC VLASTNOSTÍ HADA //////////////////////////
	////////////////////////////////////////////////////////////////////////

	/**
	 * Had získá na 1000 kroků vlastnost tank, která mu umožnuje prorážet hlavou ostatní hady, které tak resetuje.
	 * Při požadavku na nastavení odolnosti na true je také zrušena vlastnost duch (pokud ji had měl).
	 *
	 * @param tank True, pokud má mít had vlastnost tank. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostTank(boolean tank) {
		if (tank) {
			duch = false;
		}
		if (tank) {
			tankCounter += 1000;
		} else {
			tankCounter = 0;
		}
		boolean returnValue = this.tank != tank;
		this.tank = tank;
		return returnValue;
	}

	/**
	 * Had se stane neviditelným pro všechny ostatní hady (pokud protihráč nehraje na stejném pc).
	 *
	 * @param neviditelnost True, pokud má mít had vlastnost neviditelnost. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostNeviditelnost(boolean neviditelnost) {
		if (neviditelnost) {
			neviditelnostCounter += 1000;
		} else {
			neviditelnostCounter = 0;
		}
		boolean returnValue = this.neviditelnost != neviditelnost;
		this.neviditelnost = neviditelnost;
		return returnValue;
	}

	/**
	 * Had se bude pohybovat jen jednou za 2 kola.
	 *
	 * @param zpomaleny True, pokud má být had zpomalený. False, pokud ne.
	 * @return True, pokud byla vlastnost změněna. Jinak false.
	 */
	public boolean setVlastnostZpomaleni(boolean zpomaleny) {
		if (zpomaleny) {
			zpomalenyCounter += 1000;
		} else {
			zpomalenyCounter = 0;
		}
		boolean returnValue = this.slowdown != zpomaleny;
		this.slowdown = zpomaleny;
		return returnValue;
	}

	/**
	 * Had zdvojnásobí svojí velikost.
	 */
	public void zdvojnasobSe() {
		prodluzHada(getDelka());
		if (velikostProVitezstvi != 0 && getDelka() >= velikostProVitezstvi) {
			score.vyhra();
		}
	}

	public boolean ziskejVlastnostTron() {
		if (tron) {
			return true;
		}
		velikostProVitezstviTemp = velikostProVitezstvi;
		velikostProVitezstvi = 0;
		blikajici = false;
		tron = true;
		tronInfo = "VSE";
		return false;
	}

	public void ztratVlastnostTron() {
		if (tron) {
			velikostProVitezstvi = velikostProVitezstviTemp;
			blikajici = blikajiciTemp;
			tron = false;
			tronInfo = "SMAZ";
		}
	}

	public boolean isTron() {
		return tron;
	}

	/**
	 * Prohodí hada s předaným hadem. Vymění si všechno včetně velikosti, pozice a směru.
	 *
	 * @param had Had, s kterým se tento had prohodí.
	 */
	public void swapSnakes(Snake had) {
		odmapujVsechnyClanky();
		had.odmapujVsechnyClanky();

		// uložení parametrů článků hada a jeho směru do temp proměnných
		List<Dimension> tempSouradniceClanku = getDimensionFromClanky(clanky);
		Direction tempSmer = direction;

		// nastavení tohoto hada podle zadaného
		swapAllClanky(had, this);
		direction = had.getSmer();

		// nastavení zadaného hada podle tohoto, uloženém v dočasném
		swapAllClanky(tempSouradniceClanku, had);
		had.direction = tempSmer;

		namapujVsechnyClanky();
		had.namapujVsechnyClanky();
	}

	/**
	 * Všechny countery sníží o 1.
	 * Pokud dosáhne counter některé z vlastností 0, vlastnost se hadovi odebere.
	 */
	private void counterAbilities() {
		if (tankCounter > 0) {
			tankCounter--;
			if (tankCounter == 0) {
				tank = false;
			}
		}
		if (jedoveStrelyCounter > 0) {
			jedoveStrelyCounter--;
			if (jedoveStrelyCounter == 0) {
				jedoveStrely = false;
			}
		}
		if (odolnostCounter > 0) {
			odolnostCounter--;
			if (odolnostCounter == 0) {
				invulnerability = false;
			}
		}
		if (obraceneKlavesyCounter > 0) {
			obraceneKlavesyCounter--;
			if (obraceneKlavesyCounter == 0) {
				obraceneKlavesy = false;
			}
		}
		if (duchCounter > 0) {
			duchCounter--;
			if (duchCounter == 0) {
				duch = false;
			}
		}
		if (neviditelnostCounter > 0) {
			neviditelnostCounter--;
			if (neviditelnostCounter == 0) {
				neviditelnost = false;
			}
		}

		if (zpomalenyCounter > 0) {
			zpomalenyCounter--;
			if (zpomalenyCounter == 0) {
				slowdown = false;
			}
		}
		if (totalIlvulnerabilityCounter > 0) {
			totalIlvulnerabilityCounter--;
			if (totalIlvulnerabilityCounter == 0) {
				setVlastnostTotalInvulnerability(false);
			}
		}
	}

	public void namapujVsechnyClanky() {
		for (Clanek clanek : clanky) {
			clanek.namapuj(model);
		}
	}

	/**
	 * Odstraní z globální mapy všechny články hada.
	 */
	public void odmapujVsechnyClanky() {
		for (Clanek clanek : clanky) {
			clanek.odmapuj(model);
		}
	}

	/**
	 * Vrátí objekt na herní ploše, který je umístěn před hadem ve směru jeho pohybu (nebo pohybu před zastavením).
	 *
	 * @param vzdalenost Počet políček před hadem, kde chceme zjistit objekt na herní ploše.
	 * @return Objekt na herní ploše nebo null, pokud zde žádný objekt není.
	 */
	public VecNaPolicku getObjektPredHadem(int vzdalenost) {
		Point p = getPolickoPredHadem();
		return model.getObjektNaHerniPlose(p.x, p.y);
	}

	/**
	 * Vrátí souřadnice políčka před hadem jako Point.
	 *
	 * @return Políčko před hadem (i pokud had stojí).
	 */
	private Point getPolickoPredHadem() {
		return model.getPolickoPred(getX(), getY(), getExistingDirection());
	}

	/**
	 * Move snake one box in his direction and check collision.
	 *
	 * @return Ability of snake to move. Does not matter, if actually moved.
	 * In other words, return true if snake moved, or did not because of nowhere direction.
	 * And return false if snake did not move because of slowdown.
	 */
	public boolean move() {
		if (zije) {
			counterAbilities();

			if (stopOneRound) {
				stopOneRound = false;
				return false;
			}
			if (slowdown) {
				stopOneRound = true;
			}

			// posun hada (přesunutím zadního článku na místo posunu nové hlavy)
			if (isMoving()) {
				posunSe();
			}
		}
		return true;
	}

	/**
	 * Zkontroluje objekty před hadem a zavolá kolize na tyto objekty.
	 * Pokud lze pokračovat na volné místo před hadem, posune se tam.
	 * Pokud má želvu a hýbe se, ztratí total invulnerability.
	 */
	private void posunSe() {
		if (turtle && isMoving()) {
			setVlastnostTotalInvulnerability(false);
		}

		Point polickoPredHadem = getPolickoPredHadem();

		// Pokud had naboural, neposune se dál.
		if (kolize(polickoPredHadem.x, polickoPredHadem.y)) {
			return;
		}

		// posun zadního článku na místo nové hlavy
		// znovu se zjistí políčko před hadem, protože se mohlo po kolizi změnit
		posunZadnihoClankuDopredu();
	}

	/**
	 * Posune zadní článek hada na volné místo před hadem.
	 * Tento článek je pak hlavou hada, protože je nultý v LinkedListu.
	 */
	private void posunZadnihoClankuDopredu() {
		// zjištění políčka před hadem
		Point polickoPredHadem = getPolickoPredHadem();

		// Zadek je poslední článek hada
		Clanek zadek = clanky.remove(clanky.size() - 1);

		// Při posunu se uvolní logické místo na jeho ploše.
		zadek.odmapuj(model);

		// A skutečně se přesune na místo nové hlavy.
		zadek.move(polickoPredHadem.x, polickoPredHadem.y);

		// Vrácení zpět do LinkedListu na 1.místo, kam hlava patří
		clanky.add(0, zadek);

		// Namapování logických informací. Na místě, kam se had posunul, je nová hlava!
		clanky.get(0).namapuj(model);
	}

	/**
	 * Zkontroluje kolize s políčkem na pozici x a y, který by měl být před hadem.
	 * Zkontroluje, zda je had v herní mapě. Pokud ne, zavolá metodu kolize s krajní stěnou.
	 * Pokud je na tomto místě objekt, had se podle toho zachová.
	 *
	 * @param x Pozice hlavy hada x.
	 * @param y Pozice hlavy hada y.
	 * @return true, pokud had narazil. False, pokud nenarazil.
	 * False může vrátit i pokud na daném místě byl objekt, ale had vyhrál.
	 * Např. sebral jídlo, nebo tankem resetoval jiného hada.
	 */
	private boolean kolize(int x, int y) {
		if (checkBorderCollision(x, y)) {
			return true;
		}

		VecNaPolicku polickoPredHadem = model.getObjektNaHerniPlose(x, y);
		if (polickoPredHadem != null) {
			if (polickoPredHadem instanceof ViceObjektu) {
				ViceObjektu viceObjektu = (ViceObjektu) polickoPredHadem;
				for (VecNaPolicku vecNaPolicku : viceObjektu.getVeciNaPolicku()) {
					if (kolizeSObjektem(vecNaPolicku)) {
						return true;
					}
				}
				return false;
			} else {
				return kolizeSObjektem(polickoPredHadem);
			}
		}
		return false;
	}

	private boolean kolizeSObjektem(VecNaPolicku objektPredHadem) {
		if (objektPredHadem instanceof Clanek) {
			return kolizeSClankem((Clanek) objektPredHadem);
		} else if (objektPredHadem instanceof Cake) {
			model.jidloSezrano(this);
			return false;
		} else if (objektPredHadem instanceof Cherry) {
			model.bonusSezran(this);
			return false;
		} else if (objektPredHadem instanceof Zed) {
			if (!duch) {
				model.resetHada(this);
				return true;
			} else {
				return false;
			}
			// pokud had hlavou narazí do laseru, zavolá se metoda na zásah hlavy laserem
		} else if (objektPredHadem instanceof Laser) {
			Laser laser = (Laser) objektPredHadem;
			boolean uspech = zasah(laser, 0);
			if (uspech) {
				laser.znicSe(); // TODO jestlize metodu zavola metoda kolize teto tridy, neodstrani se strela z modelu strel (tzn. ted to nefunguje, pokud had narazi hlavou do strely)
				return true;
			} else {
				return false; // pokud má had např. odolnost (i když v hlavě ji nikdy nemá)
			}
		} else if (objektPredHadem instanceof Rocket) {
			Rocket raketaPredHadem = (Rocket) objektPredHadem;
			usekniEfektneOcas(0);
			return true;
		}
		System.err.println("Kolize s neznámým objektem: " + objektPredHadem.getClass());
		return false;
	}

	/**
	 * Zkontroluje, zda had nenarazil na stěny herní plochy a případně hada resetuje.
	 * Pokud je had svobodný, místo nárazu do stěny herní plochy se objeví zrcadlově na druhé straně herní plochy.
	 * Pštros se může pohybovat i mimo herní plochu.
	 *
	 * @return true, pokud had narazil do stěny.
	 * Vrátí true i v případě, kdy se teleportuje na druhou stranu a had se neresetuje.
	 */
	public boolean checkBorderCollision(int x, int y) {
		// pokud je had uvnitř herní plochy, vrátí false
		if (model.isVHerniMape(x, y)) {
			return false;
		}

		if (pstros) {
			return false;
		}
		// Pokud je had svobodný, objeví se při překlenutí stěny herní plochy na druhé straně.
		if (svobodny) {
			if (x < 0) {
				// hlava se posune na místo mimo herní plochu a teprve poté se teleportuje na druhou stranu mapy
				posunZadnihoClankuDopredu();
				clanky.get(0).setX(model.getSirkaLogicka() - 1);
				return true;
			} else if (x > model.getSirkaLogicka() - 1) {
				posunZadnihoClankuDopredu();
				clanky.get(0).setX(0);
				return true;
			} else if (y < 0) {
				posunZadnihoClankuDopredu();
				clanky.get(0).setY(model.getVyskaLogicka() - 1);
				return true;
			} else if (y > model.getVyskaLogicka() - 1) {
				posunZadnihoClankuDopredu();
				clanky.get(0).setY(0);
				return true;
			}
			System.err.println("Chyba při kolizi s krajní stěnou.");
		}

		odmapujVsechnyClanky();
		model.resetHada(this);
		return true;
	}

	private boolean kolizeSClankem(Clanek clanek) {
		Snake nabouranyHad = clanek.getHad();
		if (duch || nabouranyHad.duch) {
			return false;
		}
		int cisloClanku = nabouranyHad.getClanek(clanek.getX(), clanek.getY());
		// jestliže narazil do hlavy jiného hada
		if (cisloClanku == 0) {
			return kolizeHlav(nabouranyHad);
		} // jestliže narazil do článku některého hada
		else {
			if (tank) {
				model.resetHada(nabouranyHad);
				boolean zemrel = zkratHada(1);
				if (zemrel || nabouranyHad.equals(this)) {
					return true;
				}
				return false;
			} else {  // normální srážka hada
				model.resetHada(this);
				return true;
			}
		}
	}

	/**
	 * Resetuje menšího hada. Při stejné velikosti resetuje oba.
	 * Bere v úvahu vlastnost tank, která vždy přemůže hada bez tanku.
	 * Pokud mají tank oba, opět se posuzují jejich velikosti.
	 *
	 * @param had Had, který bude porovnávám s tímto hadem.
	 */
	private boolean kolizeHlav(Snake had) {
		if (tank == false && had.tank == false) {
			return resetujMensihoHada(had);
		} else if (tank && had.tank == false) {
			model.resetHada(had);
			zkratHada(1);
			return false;
		} else if (tank == false && had.tank) {
			model.resetHada(this);
			had.zkratHada(1);
			return true;
		} else if (tank && had.tank) {
			return resetujMensihoHada(had);
		}
		return false;
	}

	/**
	 * Resetuje menšího hada.
	 * Při stejné velikosti resetuje oba hady.
	 *
	 * @param had Had, jehož hlava se bude porovnávat s tímto hadem.
	 */
	private boolean resetujMensihoHada(Snake had) {
		// pokud je větší had ten druhý, umírám já
		if (getDelka() < had.getDelka()) {
			model.resetHada(this);
			//   model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Vyhrál %s", this.jmeno, jmeno, jmeno));
			return true;
		} // pokud jsem větší já, umírá druhý had
		else if (getDelka() > had.getDelka()) {
			model.resetHada(had);
			//   model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Vyhrál %s", this.jmeno, jmeno, this.jmeno));
			return false;
			// pokud jsme stejně velcí, umíráme oba
		} else {
			model.resetHada(this);
			model.resetHada(had);
			//    model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Umřeli oba.", this.jmeno, jmeno));
			return true;
		}
	}

	/**
	 * Vrátí informace pro poslání o daném článku hada.
	 *
	 * @param clanekHada Článek hada od 0 do délky hada - 1.
	 * @return souřadnice x + Informace.ODDELOVAC3 + souřadnice y + Informace.ODDELOVAC3 + barva článku + Informace.ODDELOVAC3 + dodatečná informace + Informace.ODDELOVAC3 + směr článku[hlavy]
	 * Null vrátí, pokud daný článek hada neexistuje.
	 */
	private String getClanek(int clanekHada) {
		if (clanekHada > getDelka() - 1) {
			return null;
		}

		if (duch) {
			return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.DUCH + Informace.ODDELOVAC3 + Informace.NIC;
		}
		if (clanekHada == 0) {
			if (tank) {
				return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.HLAVA_TANKU + Informace.ODDELOVAC3 + Direction.prevedSmerNaCislo(getExistingDirection());
			} else {
				return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.HLAVA + Informace.ODDELOVAC3 + Direction.prevedSmerNaCislo(getExistingDirection());
			}
		}
		if (invulnerability || (totalInvulnerability && clanekHada <= (velikostProVitezstvi - NUMBER_OF_EXTRA_CELLS))) {
			return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.ODOLNOST + Informace.ODDELOVAC3 + Informace.NIC;
		}
		return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.CLANEK + Informace.ODDELOVAC3 + Informace.NIC;
	}

	/**
	 * Vrátí instrukce pro kreslení, které mohou být poslány klientům.
	 * Instrukce pro kreslení se týkají všech článků hada a jeho střel.
	 *
	 * @param invisible Při true posílá i neviditelné hady.
	 * @return Instrukce pro kreslení.
	 */
	public String getInstrukceProKresleni(boolean invisible) {
		String instrukce = "";

		// instrukce pro poslání článků hada
		if (!neviditelnost || invisible) {
			for (int i = 0; i < getDelka(); i++) {
				instrukce += Informace.HAD + Informace.ODDELOVAC3;
				instrukce += getClanek(i);
				if (neviditelnost) {
					instrukce += Informace.ODDELOVAC3 + Informace.NEVIDITELNOST;
				}
				instrukce += Informace.ODDELOVAC2;
			}
		}
		return instrukce;
	}

	/**
	 * Vykreslí všechny články hada, jeho střely a rakety.
	 * Kreslí podle vlastností hada. Např. duch = menší články.
	 * Pokud je velký a had má vlastnost blikající, ocas bliká.
	 *
	 * @param g
	 */
	public void draw(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;

		for (int i = 0; i < clanky.size(); i++) {
			g2d.setPaint(getBarva(i)); // nastavení barvy pro konkrétní článek hada
			if (neviditelnost) {
				if (player.getPacket() == null && player.getAI() == null) { // pokud tento had patří serveru a hráči (ne AI), vykreslí se
					Clanek.NastavNeviditelneKresleni(g2d);
				} else { // pokud klientovi, nevykreslí se
					return;
				}
			}
			if (duch) {
				Clanek.paintDuch(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
			} else if ((totalInvulnerability && i <= (velikostProVitezstvi - NUMBER_OF_EXTRA_CELLS))) {
				Clanek.paintOdolny(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
			} else if (invulnerability && i != 0) {
				Clanek.paintOdolny(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
			} else {
				// vykreslení v případě žádných vlastností ovlivňující vzhled těla
				if (i == 0) {
					if (tank) {
						Clanek.paintHlavaTank(g2d, getX(), getY(), tloustka, getExistingDirection());
					} else {
						Clanek.paintHlava(g2d, getX(), getY(), tloustka, getExistingDirection());
					}
				} else {
					Clanek.paint(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
				}
			}
		}
		// nastavení průhlednosti zpět na normální hodnotu
		g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));
	}
}
